iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
AI Engineering

30天拆Agent:從Repo看設計系列 第 5

Day 5|Claude Code 的強制阻擋設定

  • 分享至 

  • xImage
  •  

上一篇介紹 anthropics/oncall-kit 時,有一個設計我特別喜歡:setup 被拆成五個 Phase,而且每個 Phase 都要求 Human sign-off。

Phase 0 Discover
        ↓
Human Gate
        ↓
Phase 1 Mine
        ↓
Human Gate
        ↓
Phase 2 Interview
        ↓
Human Gate
        ↓
Phase 3 Validate
        ↓
Human Gate
        ↓
Phase 4 Install

oncall-setup/SKILL.md 明確要求,每一個 Phase 做完後都要停下來,等待使用者確認,不能直接進入下一個 Phase。Repo 的 CLAUDE.md 也再次規定:「Every setup phase ends at a gate」,而且不能因為使用者要求趕快做完,就一次執行兩個 Phase。

這是一個很漂亮的 Human Gate。

但上一篇寫完後,我還留下一個問題:

這個 Gate 是 Claude「被要求停下來」,還是 Claude Code runtime 真的會阻止它?

兩者其實不一樣。


oncall-kit 的 Gate,目前主要還是 instruction

oncall-kit 的做法大致是:

SKILL.md
↓
告訴 Claude 現在是哪個 Phase
↓
完成工作
↓
STOP
↓
等待 Human sign-off
↓
進下一個 Phase

這裡的 STOP 是寫給模型看的 instruction。

它不是 Claude Code 的特殊語法,也不代表 runtime 自動建立一個 lock。

也就是說,它比較接近:

「沒有核准,不要繼續。」

而不是:

「沒有核准,你就算想繼續也執行不了。」

oncall-kit 雖然也有 Hook,但目前 Repo 裡的 hooks/hooks.json 只有 SessionStart

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "startup",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/hooks/first-run.sh"
          }
        ]
      }
    ]
  }
}

它的用途是 session 啟動時執行 first-run.sh,不是拿來鎖住 Phase。

所以比較準確的說法應該是:

oncall-kit 用 Skill + Human sign-off 做 workflow Gate;Claude Code 本身其實還能再加一層 runtime Gate,只是這個 Repo 沒有這樣實作。

而 Claude Code 最適合拿來做這件事的,就是 PreToolUse Hook。


PreToolUse:Tool 執行以前先檢查

Claude Code 的 Hook 會在 Agent 執行流程中的特定時間點被觸發。

其中:

PreToolUse

就是在 Tool 真正執行以前觸發,而且官方文件直接寫明:

Before a tool call executes. Can block it.

流程大概是:

Claude 決定呼叫 Tool
        ↓
PreToolUse
        ↓
檢查條件
        ↓
允許 / 拒絕
        ↓
Tool 才可能真正執行

例如 Claude 想執行:

mcp__routines__create

Claude Code 可以先跑一支檢查程式。

如果程式回傳:

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "Phase 4 尚未取得人工核准"
  }
}

Claude Code 會直接 block 這次 Tool call。

官方文件自己的範例也是這樣做:在 PreToolUse 攔截 Bash,檢查到危險的 rm -rf 後回傳 permissionDecision: "deny";Claude Code 收到結果後會阻止 Tool 執行,並把拒絕原因交回 Claude。

這就和在 SKILL.md 寫:

請不要執行這個工具

有本質差異。


如果替 oncall-kit 加上這個 Gate

先說明:下面是延伸設計,不是 oncall-kit Repo 現有功能。

oncall-kit 原本的 Phase 4 並沒有提供一個 mcp__routines__create Tool。

它實際上的做法是讓 Claude 產生 routine,最後由 Human 把 routine 貼進 Slack。README 也明確說明 human 決定、human deploy,而且整套 on-call Agent 預設採 read-only。

但假設之後把它產品化,真的提供一個 Tool:

mcp__routines__create

讓 Agent 可以直接建立 routine。

我們就可以規定:

Phase 4 未核准
→ 可以產生 routine 草稿
→ 可以修改內容
→ 可以讓人 review
→ 但是不能真的 create

這時可以直接在專案的:

.claude/settings.json

設定:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "mcp__routines__create",
        "hooks": [
          {
            "type": "command",
            "command": "python .claude/hooks/check-oncall-gate.py"
          }
        ]
      }
    ]
  }
}

Claude Code 的 Hook 可以放在 .claude/settings.json,代表整個 project 都會使用;也可以放在 user settings、plugin 或 managed settings。

接著建立:

.claude/hooks/check-oncall-gate.py

最簡單的版本可以是:

import json
import os
import sys

event = json.load(sys.stdin)

approved = (
    os.environ.get("ONCALL_PHASE4_APPROVED") == "1"
)

if (
    event.get("tool_name") == "mcp__routines__create"
    and not approved
):
    print(json.dumps({
        "hookSpecificOutput": {
            "hookEventName": "PreToolUse",
            "permissionDecision": "deny",
            "permissionDecisionReason":
                "Phase 4 尚未取得人工核准"
        }
    }))

實際流程會變成:

Claude:
我要呼叫 mcp__routines__create
        ↓
Claude Code:
先觸發 PreToolUse
        ↓
check-oncall-gate.py
        ↓
Phase 4 approved?
        ↓
     no       yes
      ↓        ↓
    deny     繼續一般
             permission flow

這裡有一個很重要的細節。

Python Script 並不是「自己攔住 Tool」。

真正負責 enforcement 的仍然是 Claude Code runtime。

Script 只是回答:

這次 Tool call 可以過嗎?

然後 Claude Code 根據 Hook 結果決定是否執行。

官方文件也特別說明,如果 Hook 沒有輸出 decision,只是正常 exit 0,並不代表自動 approve,而是回到 Claude Code 原本的 permission flow。


approval 不應該由 Claude 自己控制

上面的範例用了:

ONCALL_PHASE4_APPROVED=1

只是為了讓程式碼簡單。

真正做 production Gate 時,最重要的其實不是 Hook,而是:

誰有權改 approval?

假設我們把狀態存在:

{
  "phase4_approved": false
}

然後 Claude 同時有權限修改這個檔案。

那 Gate 就沒有意義了。

因為可能變成:

Claude 發現 create 被擋
        ↓
修改 phase4_approved
false → true
        ↓
重新呼叫 create

這就像是:

門有上鎖,但是鑰匙也交給被鎖在門外的人。

所以正式環境比較合理的方式是:

Human
↓
Approval Service
↓
approved = true

而 Claude Code 的 Hook:

PreToolUse
↓
讀 approval state
↓
但沒有權限修改它

例如 approval 可以放在:

  • CI/CD approval
  • Deployment platform
  • Internal approval API
  • 管理者操作的 DB
  • Agent 只有 read permission 的 storage

這才是真正的 Human Gate。


Permission 與 Hook 不一樣

Claude Code 還有另一層:

permissions

例如:

{
  "permissions": {
    "deny": [
      "mcp__production__delete"
    ]
  }
}

這適合處理:

這個 Tool 永遠不應該給 Agent 使用。

Claude Code 的 permission 還有 allowaskdeny,而且 deny 的優先權最高;只要任何 scope 有 deny,其他地方就不能再用 allow 把它打開。

所以可以簡單區分:

永遠不能做
→ permissions.deny

有條件才能做
→ PreToolUse

例如:

刪除 production DB
→ 永遠不能做
→ permissions.deny

建立 routine
→ Human approve 後才能做
→ PreToolUse

deploy production
→ change request approved 後才能做
→ PreToolUse

這樣就比全部塞進 CLAUDE.md 清楚很多。


為什麼不只寫在 Skill 裡?

Claude Code 現在其實支援直接在 Skill frontmatter 裡設定 Hook。

例如:

---
name: oncall-setup

hooks:
  PreToolUse:
    - matcher: "mcp__routines__create"
      hooks:
        - type: command
          command: "python .claude/hooks/check-oncall-gate.py"
---

Skill 被 invoke 後,這個 Hook 會註冊,而且持續到該 session 結束。

所以技術上完全可以寫成:

oncall-setup Skill
├── SOP
├── Phase 定義
└── Gate Hook

很方便。

但是如果這是一個真正重要的 security policy,我反而不會只放 Skill。

因為 Skill Hook 的前提是:

這個 Skill 有被 invoke

假設 Claude 從其他 Skill、其他 prompt,甚至直接操作 Tool,就不一定受到這個 Skill-level Hook 保護。

如果規則是:

只要在這個專案裡,建立 routine 前都必須有人核准。

那比較適合放:

.claude/settings.json

如果規則是:

整間公司的 Claude Code 都不能繞過這個限制。

那就再往上放到:

Managed Settings

Managed Settings:連使用者都不能自己關掉

Claude Code 的設定有不同 scope:

~/.claude/settings.json
→ 個人

.claude/settings.json
→ Project

Managed Settings
→ Organization

而 Managed Settings 的優先權最高。

Claude Code 官方文件明確指出,管理員部署的 managed settings,user 與 project settings 不能覆蓋;managed permission deny 也不能被 --allowedTools 打開。

Hook 也有類似機制。

管理員可以設定:

allowManagedHooksOnly

讓 user、project、local 等一般 Hook 不再生效,只允許管理端控制的 Hook。

因此企業真正需要「不能自己拔掉的 Gate」時,可以變成:

Managed Settings
        ↓
PreToolUse
        ↓
Company approval service
        ↓
approved?
        ↓
   no        yes
    ↓         ↓
  deny      Tool

這就不是:

我們要求 Claude 記得不要做

而是:

公司把這條規則放在 Claude Code runtime 外圍

Stop Hook 不是 Human Gate

這裡還有一個很容易搞混的地方。

Claude Code 也有:

Stop

Hook。

看到 oncall-kit 裡一直寫:

STOP and wait for sign-off

很容易直覺認為應該用 Stop Hook。

其實不是。

Claude Code 的 Stop Hook 是:

Claude 準備結束這一輪回答
↓
觸發 Stop Hook

所以 block Stop 通常代表:

你還不能結束,繼續處理。

而不是:

你不能執行這個 Tool。

官方 lifecycle 也明確區分:PreToolUse 是 Tool 執行前,可以 block;Stop 則是在 Claude 完成 response 時觸發。

所以做 Human Gate,如果目的是:

未核准不得產生副作用

真正該擋的是:

PreToolUse

不是 Stop。

References


上一篇
Day 4|Anthropic oncall-kit:從 Repo 架構理解 Skill、Memory、Human Gate 與 Replay Eval
下一篇
Day 6|HolmesGPT:當 Agent 自己掌握 Tool Loop,控制點會放在哪裡?
系列文
30天拆Agent:從Repo看設計6
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言